﻿#region using
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using AwesomeTanks.Sources.GameElements;
#endregion


namespace AwesomeTanks.Sources.Particles
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
	public class ParticleManager<T>
    {
        #region Fields
        // This delegate will be called for each particle.
		private Action<Particle> updateParticle;
		private CircularParticleArray particleList;
        #endregion

        #region Constructors
        /// <summary>
		/// Allows creation of particles.
		/// </summary>
		/// <param name="capacity">The maximum number of particles. An array of this size will be pre-allocated.</param>
		/// <param name="updateParticle">A delegate that lets you specify custom behaviour for your particles. Called once per particle, per frame.</param>
		public ParticleManager(int capacity, Action<Particle> updateParticle)
		{
			this.updateParticle = updateParticle;
			particleList = new CircularParticleArray(capacity);

			// Populate the list with empty particle objects, for reuse.
			for (int i = 0; i < capacity; i++)
				particleList[i] = new Particle();
		}
        #endregion

        #region Methods
        /// <summary>
        /// Swaps the specified list.
        /// </summary>
        /// <param name="list">The list.</param>
        /// <param name="index1">The index1.</param>
        /// <param name="index2">The index2.</param>
        private static void Swap(CircularParticleArray list, int index1, int index2)
        {
            var temp = list[index1];
            list[index1] = list[index2];
            list[index2] = temp;
        }

        /// <summary>
        /// Destroys all particles
        /// </summary>
        public void Clear()
        {
            particleList.Count = 0;
        }

        public int ParticleCount
        {
            get { return particleList.Count; }
        }

        #endregion

        #region Update and Draw
        /// <summary>
		/// Update particle state, to be called every frame.
		/// </summary>
		public void Update()
		{
			int removalCount = 0;
			for (int i = 0; i < particleList.Count; i++)
			{
				var particle = particleList[i];
				updateParticle(particle);
				particle.PercentLife -= 1f / particle.Duration;

				// sift deleted particles to the end of the list
				Swap(particleList, i - removalCount, i);

				// if the particle has expired, delete this particle
				if (particle.PercentLife < 0)
					removalCount++;
			}
			particleList.Count -= removalCount;
		}

		/// <summary>
		/// Draw the particles.
		/// </summary>
		public void Draw(SpriteBatch spriteBatch)
		{
			for (int i = 0; i < particleList.Count; i++)
			{
				var particle = particleList[i];

				Vector2 origin = new Vector2(particle.SourceRectangle.Value.Width / 2, particle.SourceRectangle.Value.Height / 2);

				spriteBatch.Draw(particle.Texture, particle.Position, particle.SourceRectangle, particle.Color, particle.Rotation, origin, particle.Scale, 0, 0);
			}
		}

		public void CreateParticle(Texture2D texture, Vector2 position,  Rectangle? sourceRectangle, Color tint, float duration, float scale, T state, float theta = 0)
		{
			CreateParticle(texture, position, sourceRectangle, tint, duration, new Vector2(scale), state, theta);
		}

		public void CreateParticle(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color tint, float duration, Vector2 scale, T state, float theta = 0)
		{
			Particle particle;
			if (particleList.Count == particleList.Capacity)
			{
				// if the list is full, overwrite the oldest particle, and rotate the circular list
				particle = particleList[0];
				particleList.Start++;
			}
			else
			{
				particle = particleList[particleList.Count];
				particleList.Count++;
			}

			// Create the particle
			particle.Texture = texture;
			particle.Position = position;
			particle.Color = tint;
            particle.SourceRectangle = sourceRectangle;

			particle.Duration = duration;
			particle.PercentLife = 1f;
			particle.Scale = scale;
			particle.Rotation = theta;
			particle.State = state;
		}

        #endregion



        /// <summary>
        /// 
        /// </summary>
		public class Particle : Sprite
        {
            #region Fields
            public new Texture2D Texture;
			public new Vector2 Position;
			public new float Rotation;
            public new Rectangle? SourceRectangle = null;
			public new Vector2 Scale = Vector2.One;

			public new Color Color;
			public float Duration;
			public float PercentLife = 1f;
			public T State;
            #endregion
        }

        /// <summary>
        /// Represents a circular array with an arbitrary starting point. It's useful for efficiently overwriting
        /// the oldest particles when the array gets full. Simply overwrite particleList[0] and advance Start.
        /// </summary>
		private class CircularParticleArray
        {
            #region Fields
            private int start;
			public int Start
			{
				get { return start; }
				set { start = value % list.Length; }
			}

			public int Count { get; set; }
			public int Capacity { get { return list.Length; } }
			private Particle[] list;
            #endregion

            #region Constructors
            public CircularParticleArray() { }  // for serialization

            /// <summary>
            /// Initializes a new instance of the <see cref="CircularParticleArray"/> class.
            /// </summary>
            /// <param name="capacity">The capacity.</param>
			public CircularParticleArray(int capacity)
			{
				list = new Particle[capacity];
			}
            #endregion

            /// <summary>
            /// Gets or sets the <see cref="Particle"/> with the specified i.
            /// </summary>
            /// <value>
            /// The <see cref="Particle"/>.
            /// </value>
            /// <param name="i">The i.</param>
            /// <returns></returns>
            public Particle this[int i]
			{
				get { return list[(start + i) % list.Length]; }
				set { list[(start + i) % list.Length] = value; }
			}
		}
	}
}
